package com.gitee.wsl.doc.prosemirror.transform.ext

import com.gitee.wsl.doc.prosemirror.exception.RangeError
import com.gitee.wsl.doc.prosemirror.model.Whitespace
import com.gitee.wsl.doc.prosemirror.model.attr.Attrs
import com.gitee.wsl.doc.prosemirror.model.fragment.Fragment
import com.gitee.wsl.doc.prosemirror.model.fragment.Slice
import com.gitee.wsl.doc.prosemirror.model.node.Node
import com.gitee.wsl.doc.prosemirror.model.node.NodeType
import com.gitee.wsl.doc.prosemirror.model.node.ext.canChangeType
import com.gitee.wsl.doc.prosemirror.transform.TransformAble
import com.gitee.wsl.doc.prosemirror.transform.step.ReplaceAroundStep


fun<T: TransformAble> T.setBlockType( from: Int, to: Int, type: NodeType, attrs: Attrs?) =
    setBlockType(from, to, type, { attrs })

fun<T: TransformAble> T.setBlockType( from: Int, to: Int, type: NodeType, attrs: (Node) -> Attrs?) = apply {
    val tr = this
    if (!type.isTextblock) throw RangeError("Type given to setBlockType should be a textblock")
    val mapFrom = tr.steps.size
    tr.doc.nodesBetween(from, to) { node, pos, parent, index ->
        val attrsHere = attrs(node)
        if (
            node.isTextblock &&
            !node.hasMarkup(type, attrsHere) &&
            tr.doc.canChangeType(tr.mapping.slice(mapFrom).map(pos), type)
        ) {
            var convertNewlines: Boolean? = null
            if (type.schema.linebreakReplacement != null) {
                val pre = type.whitespace == Whitespace.PRE
                val supportLinebreak = type.schema.linebreakReplacement?.let { type.contentMatch.matchType(it) } != null
                if (pre && !supportLinebreak) {
                    convertNewlines = false
                } else if (!pre && supportLinebreak) {
                    convertNewlines = true
                }
            }

            // Ensure all markup that isn't allowed in the new node type is cleared
            if (convertNewlines != null && !convertNewlines) tr.replaceLineBreaks(node, pos, mapFrom)
            tr.clearIncompatible( tr.mapping.slice(mapFrom).map(pos, 1), type, null, convertNewlines === null)
            val mapping = tr.mapping.slice(mapFrom)
            val startM = mapping.map(pos, 1)
            val endM = mapping.map(pos + node.nodeSize, 1)
            tr.step(
                ReplaceAroundStep(
                    from = startM,
                    to = endM,
                    gapFrom = startM + 1,
                    gapTo = endM - 1,
                    slice = Slice(Fragment.from(type.create(attrsHere, null as Fragment?, node.marks)), 0, 0),
                    insert = 1,
                    structure = true
                )
            )
            if (convertNewlines == true) tr.replaceNewlines( node, pos, mapFrom)
            false
        } else {
            true
        }
    }
}
